package dexter.core.front;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.Hashtable;
import java.util.Properties;
import java.util.Vector;

import dexter.core.client.FinishListener;
import dexter.core.common.FinishParams;
import dexter.core.common.FinishResult;
import dexter.core.common.InitParams;
import dexter.core.common.InitResult;
import dexter.core.host.HostAddress;
import dexter.net.Producer;
import dexter.net.msg.common.RequestListener;
import dexter.net.msg.common.RequestParams;
import dexter.net.msg.common.ResponseParams;
import dexter.testing.EvaluationValues;
import dexter.util.ByteWrapper;
import dexter.util.Configuration;

import dexter.aware.Monitor;
import dexter.aware.Selector;
import dexter.aware.msg.StateParams;
import dexter.aware.policy.StatePolicy;

public class FrontListener implements RequestListener {
	
	private final int TIMEOUT = 100;

	private FrontEnd front;
	private Properties props;
	
	private Collection<String> busyCoords = new Vector<String>();
	
	public FrontListener(FrontEnd front) {
		this.front = front;
		this.props = front.getConfig();
		front.setStatus("STARTING LISTENER ("+props+")");
	}
	
	public ResponseParams service(RequestParams params) {
		
		String info = getTS()+"> FrontListener execute : "+params+" in Thread : "+Thread.currentThread().getId();
		
		ResponseParams tr = null;
		
		if (params instanceof InitParams) {
		  System.out.println(getTS()+"> INIT START > "+info);	
		  InitParams iparams = (InitParams) params;
		  if (iparams.isRemote()) tr = initRemoteMode(iparams);
		  else tr = initLocalMode(iparams);
		  System.out.println(getTS()+"> INIT END > "+info);	
		}
		
		if (params instanceof FinishParams) {
			 System.out.println(getTS()+"> FINISH START > "+info);	
			FinishParams fparams = (FinishParams) params;
			if (fparams.isRemote()) tr = finishRemoteMode(fparams);
			else tr = finishLocalMode(fparams);
			 System.out.println(getTS()+"> FINISH END > "+info);	
	    }		
		
		if (params instanceof StateParams) {
			StateParams sparams= (StateParams) params;
			HostAddress host = sparams.getHost();
			System.out.println("STATE RECEIVED FROM "+host.getAddress().getHostName()+" -> "+sparams);
			int[] state = sparams.getState();
			Monitor.persist(host, state);
		}
		
		
		
		return tr;
		
	}

	/*******  LOCAL MODE  *******/
	
	private InitResult initLocalMode(InitParams iparams) {
		  
		  front.setStatus("STARTING LOCAL MODE ("+iparams+")");
		  
		  //groupid
		  String gid = iparams.getId();
		  //byte[] classbytes = iparams.getClassBytes();
		  
		  //is validated?
	      boolean validated = FrontEnd.check(gid);
	      
	      String job = "NO JOB";
	      String info = "Group ("+gid+") is";
	      if (validated) {
	    	  info+= " validated";
	    	  job = front.generateJob(gid);
	      }
	      else info+= "not registered";
		  
//	      try {
//			ByteWrapper.saveClassFile("jobs"+File.separator+job, gid+".class", classbytes);
//		} catch (IOException e) {
//			e.printStackTrace();
//		}
	      
		
			
	      return new InitResult(validated, info, job);
	}
	
	private ResponseParams finishLocalMode(FinishParams params) {
		
		front.setStatus("FINISHING LOCAL MODE ("+params+")");
		
		String jobId = params.getJobId(); 
		EvaluationValues localResult = params.getLocalResult();
		String sourceIp = params.getSourceIp();
		String classname = params.getClassName();
		byte[] classbytes = params.getClassBytes();
		
		String groupId = front.finishJob(jobId);
		boolean ok = (groupId!=null);
			
		//SAVE EXPERIMENT
		char spr = File.separatorChar;
		String path = "jobs"+spr+groupId+spr+jobId+spr+classname+".class";
		
		String experimentId = null;
		try {
		experimentId = FrontEnd.saveExp(groupId, path, false, localResult);
		FrontEnd.saveJob(jobId, groupId, sourceIp);
		ByteWrapper.saveClassFile(path, classbytes);
		} catch(Exception e) {
			System.out.println(getTS()+"> Test ("+jobId+") save error");
			ok = false;
			e.printStackTrace();
		}
		
		FinishResult result = new FinishResult(ok, experimentId, localResult);
		return result;
	}

	/*******  REMOTE MODE  *******/
	
	private InitResult initRemoteMode(InitParams iparams) {
		
		front.setStatus("STARTING REMOTE MODE ("+iparams+")");
	      return initLocalMode(iparams);
	}
	
	private ResponseParams finishRemoteMode(FinishParams params) {
		
		front.setStatus("FINISHING REMOTE MODE ("+params+")");
		
		//Properties props = PropertiesReader.getDefaultProperties();
		
		String jobId = params.getJobId(); 
		String groupId = params.getGroupId();
		props.setProperty("session_id", jobId);
		//EvaluationValues localResult = params.getLocalResult();
		String sourceIp = params.getSourceIp();
		String classname = params.getClassName();
		byte[] classbytes = params.getClassBytes();
	
		props.setProperty("node_classname", classname);
		props.put("node_bytes", classbytes);
		
		boolean ok = true;
		
		//SAVE NODE FILE
		char spr = File.separatorChar;
		String path = "jobs"+spr+groupId+spr+jobId+spr+classname+".class";
		props.put("node_filepath", path);
		try {
		  ByteWrapper.saveClassFile(path, classbytes);
		} catch(Exception e) {
			System.out.println(getTS()+"> ERROR SAVING ("+path+")");
			ok = false;
			e.printStackTrace();
			return null;
		}
		
		//EXECUTES A NEW TEST
	
		EvaluationValues remoteResult = null;
		try {			
			remoteResult = executeRemoteTest(params);
		} catch(Exception e) {
			ok = false;
			System.out.println(getTS()+"> Test  ("+jobId+") execution error");
			e.printStackTrace();
		}
		
		front.finishJob(jobId);
		if (ok) ok = (groupId!=null);
		
		//SAVE EXPERIMENT	  
		String experimentId = null;
		try {
		  //String path = "jobs"+File.separator+jobId+File.separator+groupId+".class";
		  experimentId = FrontEnd.saveExp(groupId, path, true, remoteResult);
		  FrontEnd.saveJob(jobId, groupId, sourceIp);
		
		} catch(Exception e) {
			System.out.println(getTS()+"> EXCEPTION SAVING DATABASE EXP : ("+jobId+", "+groupId+")");
			ok = false;
			//e.printStackTrace();
		}
		
		FinishResult result = new FinishResult(ok, experimentId, remoteResult);
		return result;
	}
	
	private synchronized EvaluationValues executeRemoteTest(FinishParams fparams) throws Exception {
				
		//GenericTest tester = TestFactory.createTest(props, Thread.currentThread().getContextClassLoader());
		
		System.out.println(getTS()+"> FrontListener : Searching Nodes...");
		String host = holdCoord();
		
		int port = Configuration.getCoordPort();
		front.setStatus("FRONT: SENDING REMOTE TEST ("+host+":"+port+")");
		System.out.println(getTS()+"> FrontListener : Starting Test...");
		
		FinishListener flistener = new FinishListener();
		Producer producer = new Producer(host,port);
		producer.send(fparams, flistener);
		
		int count = 0;
		while(!flistener.hasResult() && count < TIMEOUT) {
			try { Thread.sleep(1000); } catch (Exception e) {}
			count++;
		}
		
		FinishResult result = flistener.getResult();
		EvaluationValues er = result.getRemoteResult();
		
		System.out.println(getTS()+"> FrontEnd : Returning results...");
		if (er!=null) front.setStatus("FRONT: FINISHING REMOTE TEST ("+er.getLog()+")");
		else front.setStatus("FRONT: ERROR IN REMOTE TEST");
		
		releaseCoord(host);
		
		return er;		
		
	}

	private synchronized String holdCoord() {
		String host = "localhost";
		StatePolicy policy = new StatePolicy(new int[]{10,10,5});
		Hashtable<String,int[]>  hosts = Selector.selectHosts(15, policy);
		for (String coord : busyCoords) hosts.remove(coord);
		if (hosts.size()>0) {
			host = hosts.keySet().iterator().next();
			System.out.println(getTS()+"> FrontListener : Coordinator selected :"+host);
		}
		else {
			host = Configuration.getCoordHostName();
			System.out.println(getTS()+"> FrontListener : Coordinator auxiliar :"+host);
		}
        busyCoords.add(host);
		return host;
	}
	
	private synchronized void releaseCoord(String host) {
		System.out.println(getTS()+"> FrontListener : Coordinator released :"+host);
		busyCoords.remove(host);
	}

	public String getTS() {
		String timestamp = "";
	    Date date = new Date();
	    SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yy hh:mm:ss");
	    timestamp = sdf.format(date);
	    return timestamp;
	}



	}
